<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>响应式</title>
</head>

<body>
    <script>
        // 存储副作用函数的桶
        const bucket = new WeakMap()
        // 定义一个全局变量存储被注册的副作用函数
        let activeEffect
        // effect 栈
        const effectStack = []

        // effect函数用于注册副作用函数
        function effect(fn, options = {}) {
            const effectFn = () => {
                // 调用cleanup函数完成清除工作
                cleanup(effectFn)
                // 当调用effect注册副作用函数时,将副作用函数复制给activeEffect
                activeEffect = effectFn
                // 在调用副作用函数之前将当前副作用函数压入栈中
                effectStack.push(effectFn)
                const res = fn()
                // 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把activeEffect还原为之前的值
                effectStack.pop()
                activeEffect = effectStack[effectStack.length - 1]
                // 将res作为effectFn的返回值
                return res
            }
            // 将options挂载到effectFn上
            effectFn.options = options
            // activeEffect.set 用来存储所有与该副作用函数相关的依赖集合
            effectFn.set = []
            if (!options.lazy) {
                effectFn()
            }
            return effectFn
        }

        function cleanup(effectFn) {
            for (let i = 0; i < effectFn.set.length; i++) {
                const set = effectFn.set[i]
                set.delete(effectFn)
            }
            effectFn.set.length = 0
        }

        function track(target, key) {
            if (!activeEffect) return
            let map = bucket.get(target)
            if (!map) {
                bucket.set(target, (map = new Map()))
            }
            let set = map.get(key)
            if (!set) {
                map.set(key, (set = new Set()))
            }
            // 把当前激活的副作用函数添加到依赖集合set中
            set.add(activeEffect)
            // 将其添加到activeEffect.set 数组中
            activeEffect.set.push(set)
        }

        function trigger(target, key) {
            const map = bucket.get(target)
            if (!map) return
            const effects = map.get(key)
            const effectsToRun = new Set()
            effects && effects.forEach(effectFn => {
                // 如果trigger触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
                if (effectFn !== activeEffect) {
                    effectsToRun.add(effectFn)
                }
            })
            effectsToRun.forEach(effectFn => {
                // 如果一个副作用函数存在调度器,则调用该调度器,并将副作用函数作为参数传递
                if (effectFn.options.scheduler) {
                    effectFn.options.scheduler(effectFn)
                } else {
                    effectFn()
                }
            })
        }

        function watch(source, callback, options = {}) {
            let getter
            if (typeof source === 'function') {
                getter = source
            } else {
                getter = () => traverse(source)
            }

            let oldValue, newValue

            const job = () => {
                newValue = effectFn()
                callback(newValue, oldValue)
                oldValue = newValue
            }

            const effectFn = effect(
                () => getter(),
                {
                    lazy: true,
                    scheduler: () => {
                        if (options.flush === 'post') {
                            const p = Promise.resolve()
                            p.then(job)
                        } else {
                            job()
                        }
                    }
                }
            )

            if (options.immediate) {
                job()
            } else {
                oldValue = effectFn()
            }
        }

        function traverse(value, seen = new Set()) {
            // 如果要读取的数据是原始值,或者已经被读取过了,那么什么都不做
            if (typeof value !== 'object' || value === null || seen.has(value)) return
            seen.add(value)
            for (const k in value) {
                traverse(value[k], seen)
            }
            return value
        }

        const data = {
            a: 1
        }

        const obj = new Proxy(data, {
            get(target, key) {
                track(target, key)
                return target[key]
            },
            set(target, key, newVal) {
                trigger(target, key)
                target[key] = newVal
            }
        })

        watch(obj, () => {
            console.log('数据变化了')
        })

        // watch(() => obj.a, () => {
        //     console.log('数据变化了')
        // })

        // watch(obj, () => {
        //     console.log('数据变化了')
        // }, {
        //     immediate: true
        // })

        watch(obj, () => {
            console.log('数据变化了')
        }, {
            flush: 'post' // 还可以指定为 'pre' | 'sync'
        })

        obj.a++

        console.log(11111111)

    </script>
</body>

</html>